package com.atguigu.tingshu.account.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.atguigu.tingshu.account.mapper.RechargeInfoMapper;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.common.constant.SystemConstant;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.ResultCodeEnum;
import com.atguigu.tingshu.model.account.RechargeInfo;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.vo.account.AccountLockResultVo;
import com.atguigu.tingshu.vo.account.AccountLockVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.concurrent.TimeUnit;

/**
 * @author wxb
 */
@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;

    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RechargeInfoMapper rechargeInfoMapper;

    @Override
    public IPage<UserAccountDetail> getUserRechargePage(Page<UserAccountDetail> pageParam, Long userId) {
        // 调用mapper 层方法
        return userAccountDetailMapper.getUserRechargePage(pageParam, userId);
    }
    @Override
    public IPage<UserAccountDetail> findUserRechargePage(Page<UserAccountDetail> pageParam, Long userId) {
        // 调用mapper 方法
        return userAccountDetailMapper.getUserRechargePage(pageParam,userId);
    }
    /**
     * 为指定账户充值
     * @param userId
     * @param rechargeAmount
     */
    @Override
    public void add(Long userId, BigDecimal rechargeAmount) {
        userAccountMapper.add(userId, rechargeAmount);
    }
    /**
     * 保存用户的信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void saveUserAccount(Long userId) {
        // user_account
        UserAccount userAccount = new UserAccount();
        userAccount.setUserId(userId);
        userAccountMapper.insert(userAccount);
    }

    @Override
    public BigDecimal getAvailableAmount(Long userId) {
        //1.获取账户信息
        UserAccount userAccount = this.getUserAccount(userId);
        //2.获取可用余额
        if (userAccount != null) {
            return userAccount.getAvailableAmount();
        }
        return null;
    }

    /**
     * 获取账户信息
     *
     * @param userId
     * @return
     */
    @Override
    public UserAccount getUserAccount(Long userId) {
        LambdaQueryWrapper<UserAccount> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(UserAccount::getUserId, userId);
        return userAccountMapper.selectOne(queryWrapper);
    }

    /**
     * 检查及锁定账户金额
     *
     * @param userId
     * @param accountLockVo
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AccountLockResultVo checkAndLock(Long userId, AccountLockVo accountLockVo) {
        //1.幂等性处理：setnx避免同一笔订单多次对账户进行锁定，非首次处理查询Redis得到锁定结果，将锁定结果返回
        String key = RedisConstant.ACCOUNT_MUTIPLE_CHECK + accountLockVo.getOrderNo();
        String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + accountLockVo.getOrderNo();
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, accountLockVo.getOrderNo(), 1, TimeUnit.HOURS);
        if (!flag) {
            //说明是非首次提交订单，尝试从reids中获取账户锁定的结果，直接返回
            AccountLockResultVo accountLockResultVo =
                    (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
            if (accountLockResultVo == null) {
                //删掉重复的redis
                redisTemplate.delete(key);
                throw new GuiguException(ResultCodeEnum.ACCOUNT_LOCK_RESULT_NULL);
            }
            return accountLockResultVo;
        }

        //2.根据用户ID+锁定金额对账户表中记录进行查询（余额是否充足）利用数据库“锁”机制避免并发情况下对账户多次锁定-采用“悲观锁”-行锁
        UserAccount userAccount = userAccountMapper.check(userId, accountLockVo.getAmount());
        if (userAccount == null) {
            //锁定失败
            redisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
        }

        //3.如果金额充足,执行锁定
        int count = userAccountMapper.lock(userId, accountLockVo.getAmount());
        if (count == 0) {
            //锁定失败
            redisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_LESS);
        }

        //4.检查锁定都成功，将锁定结果存入Redis，后续账户扣减、账户恢复都可以从Redis获取
        AccountLockResultVo accountLockResultVo = BeanUtil.copyProperties(accountLockVo, AccountLockResultVo.class);
        redisTemplate.opsForValue().set(lockResultKey, accountLockResultVo, 1, TimeUnit.HOURS);

        //新增日志变动记录
        this.saveUserAccountDetail(accountLockVo.getUserId(), "锁定" + accountLockVo.getContent(),
                SystemConstant.ACCOUNT_TRADE_TYPE_LOCK, accountLockVo.getAmount(), accountLockVo.getOrderNo());
        //将锁定的结果返回
        return accountLockResultVo;
    }

    /**
     * 保存账户日志变动记录
     *
     * @param userId
     * @param title
     * @param tradeType
     * @param amount
     * @param orderNo
     */
    @Override
    public void saveUserAccountDetail(Long userId, String title, String tradeType, BigDecimal amount, String orderNo) {
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(userId);
        userAccountDetail.setTitle(title);
        userAccountDetail.setTradeType(tradeType);
        userAccountDetail.setAmount(amount);
        userAccountDetail.setOrderNo(orderNo);
        userAccountDetailMapper.insert(userAccountDetail);
    }

    /**
     * 扣减账户锁定金额
     *
     * @param orderNo
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void minus(String orderNo) {
        //1.业务去重避免多次扣减
        String key = "account:minus" + orderNo;
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (flag) {
            //2.扣减锁定金额
            //2.1 从Redis中获取锁定结果对象
            String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + orderNo;
            AccountLockResultVo accountLockResultVo =
                    (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
            if (accountLockResultVo != null) {
                //2.2 扣减锁定金额
                int count = userAccountMapper.minus(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
                if (count == 0) {
                    //解除去重
                    redisTemplate.delete(key);
                    throw new GuiguException(ResultCodeEnum.LOGIN_AUTH.ACCOUNT_MINUSLOCK_ERROR);
                }
                //2.3 记录账户变动日志
                this.saveUserAccountDetail(accountLockResultVo.getUserId(), accountLockResultVo.getContent(),
                        SystemConstant.ACCOUNT_TRADE_TYPE_MINUS, accountLockResultVo.getAmount(), orderNo);

                //2.4 删除Redis中锁定结果
                redisTemplate.delete(lockResultKey);

            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void unlock(String orderNo) {
        //1.业务去重避免多次扣减
        String key = "account:plus" + orderNo;
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, orderNo, 1, TimeUnit.HOURS);
        if (!flag) {
            return;
        }
        //2.从Redis中获取锁定结果用于恢复锁定金额
        String lockResultKey = RedisConstant.ACCOUNT_CHECK_DATA + orderNo;
        AccountLockResultVo accountLockResultVo = (AccountLockResultVo) redisTemplate.opsForValue().get(lockResultKey);
        if (accountLockResultVo == null) {
            return;
        }
        int count = userAccountMapper.unlock(accountLockResultVo.getUserId(), accountLockResultVo.getAmount());
        if (count == 0) {
            //更新失败 解除去重
            redisTemplate.delete(key);
            throw new GuiguException(ResultCodeEnum.ACCOUNT_UNLOCK_ERROR);
        }
        //3.记录账户变动日志
        this.saveUserAccountDetail(accountLockResultVo.getUserId(), "解锁：" + accountLockResultVo.getContent(),
                SystemConstant.ACCOUNT_TRADE_TYPE_UNLOCK, accountLockResultVo.getAmount(), orderNo);
        //4.恢复锁定金额后，删除锁定结果，以防重复恢复
        redisTemplate.delete(lockResultKey);
    }

    /**
     * 根据订单号获取充值信息
     *
     * @param orderNo
     * @return
     */
    @Override
    public RechargeInfo getRechargeInfo(String orderNo) {
        LambdaQueryWrapper<RechargeInfo> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(RechargeInfo::getOrderNo,orderNo);
        RechargeInfo rechargeInfo = rechargeInfoMapper.selectOne(queryWrapper);
        return rechargeInfo;
    }
}